// Fireworks JavaScript Command // Install by copying to Fireworks/Configuration/Commands/ // Aaron Beall 2008-2011 - http://abeall.com // Version 1.2.1 (branched from Add Points v1.2) /* BUGS - each splitBezierAtDistance operation starts over, it would be faster if it started from the previous point - [FIXED-v1.1] pressing cancel gives error - [CHANGED-v1.2] copyObject/pasteObject to copyNode/pasteNode */ var dom = fw.getDocumentDOM(); // document object var sel = [].concat(fw.selection); // saved selection function AddPointsToCurves() { // require active document if (!dom) return false; // validate selection var paths = []; for(var s in sel){ if(sel[s] == '[object Path]') paths.push(sel[s]); } if(!paths.length) return alert('This command requires at least one selected path.'); // user input var threshold; do{ threshold = prompt("Enter a curvature threshold (1-180, smaller values = more points):",fw.AddPointsToCurves_threshold||45); }while(!validate()); function validate(){ if(threshold==null) return true; threshold = Number(threshold); if(isNaN(threshold)) return alert('Invalid input! Please enter numbers only.'); if(threshold < 1) return alert('Invalid input! Please enter a number greater than 1.'); if(threshold % 1 != 0) return alert('Invalid input! Please enter integers only.') return true; } if(threshold == null) return false; fw.AddPointsToCurves_threshold = threshold; //remember user input for next time // add points to all paths in selection var p = paths.length, c, ln, prevNode, nextNode, pt, nn; var con, nodes, newNodes; var subselect = fw.activeTool == 'Subselection', hasCurves = false, pointsAdded = false; while(p--){ c = paths[p].contours.length; while(c--){ con = paths[p].contours[c]; nodes = paths[p].contours[c].nodes; newNodes = []; ln = nodes.length; for(var n = 0; n < ln; n++){ newNodes.push(copyNode(nodes[n])); if(n == ln-1 && !con.isClosed) break; nextNode = nodes[n + 1] || newNodes[0]; if(nodes[n].succX == nodes[n].x && nodes[n].succY == nodes[n].y && nextNode.predX == nextNode.x && nextNode.predY == nextNode.y) continue; hasCurves = true; if(subselect && !(nodes[n].isSelectedPoint && nextNode.isSelectedPoint)) continue; while(true){ prevNode = newNodes[newNodes.length-1]; pt = splitBezierAtAngle(prevNode, {x:prevNode.succX, y:prevNode.succY}, {x:nextNode.predX, y:nextNode.predY}, nextNode, threshold); //alert('split '+n+', time: '+pt.time) if(pt.time >= 1 || pt.time == 0) break; newNodes.push(copyNode(nodes[n])); nn = newNodes[newNodes.length - 1]; nn.x = pt.p3.x; nn.y = pt.p3.y; nn.predX = pt.cp3.x; nn.predY = pt.cp3.y; nn.succX = pt.cp4.x; nn.succY = pt.cp4.y; nn.isCurvePoint = true; prevNode.succX = pt.cp1.x; prevNode.succY = pt.cp1.y; nextNode.predX = pt.cp2.x; nextNode.predY = pt.cp2.y; pointsAdded = true; } } if(newNodes.length > nodes.length){ var isClosed = con.isClosed; paths[p].contours[c] = new Contour(); paths[p].contours[c].isClosed = isClosed; var nlen = newNodes.length; for(var n = 0; n < nlen; n++){ paths[p].contours[c].nodes[n] = new ContourNode(); pasteNode(paths[p].contours[c].nodes[n], newNodes[n]); } } } } if(subselect && !pointsAdded) return alert("This operation produced no output. When using the Subselect tool you must select points adjacent to each other(points will be added between them) -- or select the entire object using the Pointer tool."); if(!hasCurves) return alert("This operation produced no output, because the path"+(paths.length>1?"s do ":" does ")+" not contain any curves."); if(!pointsAdded) return alert("This operation produced no output. Try a smaller threshold."); return true; } //try{ AddPointsToCurves(); //}catch(e){ alert([e, e.lineNumber, e.fileName].join("\n")) }; // copy node function copyNode(pt){ var ptCopy = {} copyProps(ptCopy, pt, ["x", "y", "succX", "succY", "predX", "predY", "randomSeed", "isSelectedPoint", "isCurvePoint", "name"]); ptCopy.dynamicInfo = []; for(var i = 0; i < pt.dynamicInfo.length; i++) ptCopy.dynamicInfo[i] = copyProps({}, pt.dynamicInfo[i], ["pressure", "duration", "velocity"]); return ptCopy; } // paste node function pasteNode(pt, ptCopy){ copyProps(pt, ptCopy, ["x", "y", "succX", "succY", "predX", "predY", "randomSeed", "isSelectedPoint", "isCurvePoint", "name"]); var dynamicInfo = []; for(var i = 0; i < ptCopy.dynamicInfo.length; i++) dynamicInfo.push(copyProps(new ContourNodeDynamicInfo(), ptCopy.dynamicInfo[i], ["pressure", "duration", "velocity"])); pt.dynamicInfo = dynamicInfo; return pt; } // copy props by name function copyProps(targetObj, sourceObj, props){ var p = props.length; while(p--) targetObj[props[p]] = sourceObj[props[p]]; return targetObj; } // splits a bezier segment(as defined by p1, cp1, cp2, p2) at a specified distance from p1 function splitBezierAtAngle(p1,cp1,cp2,p2,angle){ var P1 = p1;//{x:p1.x, y:p1.y}; var C1 = cp1;//{x:p1.succX, y:p1.succY}; var C2 = cp2;//{x:p2.predX, y:p2.predY}; var P2 = p2;//{x:p2.x, y:p2.y}; var P3 = getBezierAtAngle(angle,P2,C2,C1,P1); var t3 = P3.percent; var U = { x: (C1.x - P1.x)*t3, y: (C1.y - P1.y)*t3 } var V = { x: ((C2.x - C1.x)*t3 - U.x)*t3, y: ((C2.y - C1.y)*t3 - U.y)*t3 } var newC1 = { x: U.x + P1.x, y: U.y + P1.y } var C3 = { x: U.x + V.x + newC1.x, y: U.y + V.y + newC1.y } var R = { x: (C2.x - P2.x)*(1-t3), y: (C2.y - P2.y)*(1-t3) } var S = { x: ((C1.x - C2.x)*(1-t3) - R.x)*(1-t3), y: ((C1.y - C2.y)*(1-t3) - R.y)*(1-t3) } var newC2 = { x: R.x + P2.x, y: R.y + P2.y } var C4 = { x: R.x + S.x + newC2.x, y: R.y + S.y + newC2.y } //return {C4:C4,C3:C3,t3:t3,P3:P3}; return { time:t3, cp1:newC1, cp2:newC2, p3:P3, cp3:C3, cp4:C4 }; } // find the point along a bezier segment(as defined by p1, cp1, cp2, p2) at a specified distance from p1 function getBezierAtAngle(angle,p1,cp1,cp2,p2,steps,iterations){ //alert([p1+" = "+p1.toSource(),cp1+" = "+cp1.toSource(),cp2+" = "+cp2.toSource(),p2+" = "+p2.toSource()].join('\n')) if(!steps) var steps = 25; if(!iterations) var iterations = 5; var i, changedAngle = 0, currAngle, prevPoint, prevPrevPoint, point, currPercent; /*prevPoint = getBezier(0.000001,p1,cp1,cp2,p2); point = {x:prevPoint.x,y:prevPoint.y}; prevPoint.x -= (point.x-p2.x)*2; prevPoint.y -= (point.y-p2.y)*2; prevPoint.angle = getAngle(prevPoint,point);*/ //alert('prevPoint = '+prevPoint.toSource()) return doWalkBezier(0,1); function doWalkBezier(minPercent,maxPercent/*,startPoint*/){ //alert('walk bezier: '+minPercent+'-'+maxPercent); //if(startAngle!=undefined) //changedAngle = startAngle; //if(startPoint!=undefined) //prevPoint = startPoint; prevPoint = null; for(var i=0; i<=steps; i++){ currPercent = minPercent + (maxPercent-minPercent)*((1/steps)*i); point = getBezier(currPercent,p1,cp1,cp2,p2); point.percent = currPercent; //currAngle = prevPoint ? getAngle(prevPoint,point) : getAngle(point,getBezier(minPercent+0.000001,p1,cp1,cp2,p2)); if(prevPoint){ currAngle = getAngle(prevPoint,point); }else{ currAngle = getAngle(point,getBezier(minPercent+0.000001,p1,cp1,cp2,p2)); //alert('no prev point at '+currPercent+', angle = '+currAngle); } point.angle = currAngle; //alert(currPercent+" ("+Math.round((prevPoint||{angle:'NA'}).angle)+" - "+Math.round(currAngle)+')'+'['+changedAngle+']: '+(changedAngle+((prevPoint||{angle:'NA'}).angle - currAngle))); if(prevPoint){ point.dif = prevPoint.angle - currAngle; //dom.addNewLine({x:0,y:0},{x:point.x,y:point.y}); //alert(currAngle) changedAngle += point.dif; //alert(currPercent+": "+Math.round(prevPoint.angle)+" - "+Math.round(currAngle)+" -> "+Math.round(changedAngle)); if(Math.abs(changedAngle) == angle) return point; if(Math.abs(changedAngle) > angle){ changedAngle -= point.dif//+prevPoint.dif; return --iterations ? doWalkBezier(prevPoint.percent,point.percent/*,prevPrevPoint*/) : prevPoint; } } //prevPrevPoint = prevPoint; prevPoint = point; } //point = getBezier(maxPercent,p1,cp1,cp2,p2);; //point.percent = maxPercent; return point; } } // find a point along a bezier segment(as defined by p1, cp1, cp2, p2) at a specified percent(0-100) function getBezier(percent,p1,cp1,cp2,p2) { function b1(t) { return t*t*t } function b2(t) { return 3*t*t*(1-t) } function b3(t) { return 3*t*(1-t)*(1-t) } function b4(t) { return (1-t)*(1-t)*(1-t) } var pos = {x:0,y:0}; pos.x = p1.x*b1(percent) + cp1.x*b2(percent) + cp2.x*b3(percent) + p2.x*b4(percent); pos.y = p1.y*b1(percent) + cp1.y*b2(percent) + cp2.y*b3(percent) + p2.y*b4(percent); return pos; } // get the angle in degrees between two points function getAngle(p1,p2){ return -Math.atan2((p1.x-p2.x), (p1.y-p2.y))/(Math.PI/180); }